﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Twodi.Aspect.Interfaces;
using Microsoft.Xna.Framework;
using Twodi.Behavior;

namespace Twodi.Implemented
{
    /// <summary>
    /// Tipo de animação, seguindo o exemplo:
    /// Frames são contabilizados como 0, 1, 2.
    /// Normal: 0,1,2.
    /// BackForth: 0,1,2,1,0.
    /// Normal_Loop: 0,1,2,0,1,2,0,1,2...
    /// BackForth_Loop: 0,1,2,1,0,1,2,1,0...
    /// </summary>
    public enum AnimationType
    {
        Normal,
        BackForth,
        Normal_Loop,
        BackForth_Loop
    }

    /// <summary>
    /// Animaçao do componente.
    /// </summary>
    public class Animation : AbstractBehavior<ITextureAspect>
    {
        /// <summary>
        /// Construtor da animação.
        /// </summary>
        /// <param name="name">Nome da animação.</param>
        /// <param name="component">Componente que sera animado.</param>
        public Animation(String name, ITextureAspect component)
            : base(component)
        {
            Frame = 0;
            Type = AnimationType.Normal;
            Name = name;
            Frames = new List<AnimationFrame>();
        }

        /// <summary>
        /// Adicionar eventos do comportamento.
        /// </summary>
        protected override void AddEvents()
        {
            base.AddEvents();

            IsFinishedChanged += InvokeFinished;
        }


        protected virtual void InvokeFinished(object sender, EventArgs e)
        {
            if (IsFinished)
            {
                onFinished(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Lógica de animação.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            if (!Enabled) return;

            if (NextFrame == TimeSpan.Zero)
            {
                ConfigureFrame(gameTime);
            }
            if (gameTime.TotalGameTime >= NextFrame)
            {
                switch (Type)
                {
                    case AnimationType.Normal:
                        if (Frame != Frames.Count - 1) Frame++;
                        else if (!IsFinished) IsFinished = true;
                        break;
                    case AnimationType.BackForth:
                        BackForthLogic();
                        break;
                    case AnimationType.Normal_Loop:
                        Frame++;
                        Frame = Frame % Frames.Count;
                        break;
                    case AnimationType.BackForth_Loop:
                        BackForthLogic(true);
                        break;
                    default:
                        break;
                }
                ConfigureFrame(gameTime);
            }
        }

        /// <summary>
        /// Retornando frames.
        /// </summary>
        private bool back = false;

        /// <summary>
        /// Aguardando tempo do ultimo frame.
        /// </summary>
        private bool wait = false;

        /// <summary>
        /// Lógica responsavel por fazer comportamento "Back and Forth".
        /// </summary>
        /// <param name="loop">Deve fazer loop.</param>
        private void BackForthLogic(bool loop = false)
        {
            //Se não estiver retornando os frames, continue subindo.
            if (!back)
            {
                Frame++;
            }
            //Se frames estiverem retornando, e ainda puder retornar mais, retorne.
            else if (Frame - 1 >= 0)
            {
                Frame--;
            }

            //Se frame atual bate no count de frames, começa a retornar.
            if (Frame + 1 == Frames.Count)
            {
                back = !back;
            }

            //Se estiver no primeiro frame novamente.
            else if (Frame == 0)
            {
                //Se for para fazer loop, informar que deve continuar.
                if (loop)
                {
                    back = !back;
                }
                //Senão, se não estiver finalizado.
                else if (!IsFinished)
                {
                    //Se não foi aguardado ainda o tempo do ultimo frame, aguarde.
                    if (!wait)
                    {
                        wait = true;
                    }
                    //Se já foi aguardado, termine a animação.
                    else
                    {
                        IsFinished = true;
                    }
                }
            }
        }

        /// <summary>
        /// Configurar o próximo frame, e aplicar o Source ao componente com Textura.
        /// </summary>
        /// <param name="gameTime"></param>
        private void ConfigureFrame(GameTime gameTime)
        {
            NextFrame = gameTime.TotalGameTime + Frames[Frame].Duration;
            Component.TextureProperties.Source = Frames[frame].Source;
        }

        /// <summary>
        /// Adicionar frames a animação.
        /// </summary>
        /// <param name="frames">Frames a ser adicionados.</param>
        public void Add(params AnimationFrame[] frames)
        {
            this.Frames.AddRange(frames);
        }

        /// <summary>
        /// Nome da Animação.
        /// </summary>
        #region Attribute Name

        /// <summary>
        /// Nome da Animação.
        /// </summary>
        private String name;

        /// <summary>
        /// Property: Nome da Animação.
        /// </summary>
        public String Name
        {
            get { return this.name; }
            set
            {
                this.name = value;
                onNameChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Evento invocado quando valor de "Name" é alterado(a).
        /// </summary>
        public event EventHandler<EventArgs> NameChanged;

        /// <summary>
        /// Método responsavel por invocar o evento NameChanged;
        /// </summary>
        protected virtual void onNameChanged(EventArgs e)
        {
            if (NameChanged != null)
            {
                NameChanged(this, e);
            }
        }

        #endregion Attribute Name

        /// <summary>
        /// Tipo da Animação.
        /// </summary>
        #region Attribute Type

        /// <summary>
        /// Tipo da Animação.
        /// </summary>
        private AnimationType type;

        /// <summary>
        /// Property: Tipo da Animação.
        /// </summary>
        public AnimationType Type
        {
            get { return this.type; }
            set
            {
                this.type = value;
                onTypeChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Evento invocado quando valor de "Type" é alterado(a).
        /// </summary>
        public event EventHandler<EventArgs> TypeChanged;

        /// <summary>
        /// Método responsavel por invocar o evento TypeChanged;
        /// </summary>
        protected virtual void onTypeChanged(EventArgs e)
        {
            if (TypeChanged != null)
            {
                TypeChanged(this, e);
            }
        }

        #endregion Attribute Type

        /// <summary>
        /// Frame atual da animação.
        /// </summary>
        #region Attribute Frame

        /// <summary>
        /// Frame atual da animação.
        /// </summary>
        private int frame;

        /// <summary>
        /// Property: Frame atual da animação.
        /// </summary>
        public int Frame
        {
            get { return this.frame; }
            set
            {
                this.frame = value;
                onFrameChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Evento invocado quando valor de "Frame" é alterado(a).
        /// </summary>
        public event EventHandler<EventArgs> FrameChanged;

        /// <summary>
        /// Método responsavel por invocar o evento FrameChanged;
        /// </summary>
        protected virtual void onFrameChanged(EventArgs e)
        {
            if (FrameChanged != null)
            {
                FrameChanged(this, e);
            }
        }

        #endregion Attribute Frame

        /// <summary>
        /// Lista de Frames da Animação.
        /// </summary>
        #region Attribute Frames

        /// <summary>
        /// Lista de Frames da Animação.
        /// </summary>
        private List<AnimationFrame> frames;

        /// <summary>
        /// Property: Lista de Frames da Animação.
        /// </summary>
        public List<AnimationFrame> Frames
        {
            get { return this.frames; }
            set
            {
                this.frames = value;
                onFramesChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Evento invocado quando valor de "Frames" é alterado(a).
        /// </summary>
        public event EventHandler<EventArgs> FramesChanged;

        /// <summary>
        /// Método responsavel por invocar o evento FramesChanged;
        /// </summary>
        protected virtual void onFramesChanged(EventArgs e)
        {
            if (FramesChanged != null)
            {
                FramesChanged(this, e);
            }
        }

        #endregion Attribute Frames

        /// <summary>
        /// Tempo para o próximo frame.
        /// </summary>
        #region Attribute NextFrame

        /// <summary>
        /// Tempo para o próximo frame.
        /// </summary>
        private TimeSpan nextFrame;

        /// <summary>
        /// Property: Tempo para o próximo frame.
        /// </summary>
        public TimeSpan NextFrame
        {
            get { return this.nextFrame; }
            set
            {
                this.nextFrame = value;
                onNextFrameChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Evento invocado quando valor de "NextFrame" é alterado(a).
        /// </summary>
        public event EventHandler<EventArgs> NextFrameChanged;

        /// <summary>
        /// Método responsavel por invocar o evento NextFrameChanged;
        /// </summary>
        protected virtual void onNextFrameChanged(EventArgs e)
        {
            if (NextFrameChanged != null)
            {
                NextFrameChanged(this, e);
            }
        }

        #endregion Attribute NextFrame

        /// <summary>
        /// Animação finalizada.
        /// </summary>
        #region Attribute IsFinished

        /// <summary>
        /// Animação finalizada.
        /// </summary>
        private bool isFinished;

        /// <summary>
        /// Property: Animação finalizada.
        /// </summary>
        public bool IsFinished
        {
            get { return this.isFinished; }
            set
            {
                this.isFinished = value;
                onIsFinishedChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Evento invocado quando valor de "IsFinished" é alterado(a).
        /// </summary>
        public event EventHandler<EventArgs> IsFinishedChanged;

        /// <summary>
        /// Método responsavel por invocar o evento IsFinishedChanged;
        /// </summary>
        protected virtual void onIsFinishedChanged(EventArgs e)
        {
            if (IsFinishedChanged != null)
            {
                IsFinishedChanged(this, e);
            }
        }

        /// <summary>
        /// Evento invocado quando animação é finalizada.
        /// </summary>
        public event EventHandler<EventArgs> Finished;

        /// <summary>
        /// Método responsavel por invocar o evento Finished;
        /// </summary>
        protected virtual void onFinished(EventArgs e)
        {
            if (Finished != null)
            {
                Finished(this, e);
            }
        }

        #endregion Attribute IsFinished
    }
}
